Переключение между схемами БД

Данный документ описывает, как создавать приложения OpenXava, предоставляющие возможность переключения между схемами БД в процессе работы приложения.
В OX поддержка multi-schema реализована для Hibernate и EJB3 JPA. Отметим, что EJB2 CMP не поддерживает multi-schema.

Что такое приложение с поддержкой Multi-schema (multi-schema application)

Приложение с поддержкой multi-schema позволяет вам переключаться в процессе работы между разными схемами БД.
В частности, такая реализация позволяет вам реплицировать структуру вашей базы данных в нескольких схемах БД. При этом конкретный пользователь (другими словами, пользовательская сессия) может работать с разными данными в разных схемах БД и переключаться между ними.
Например, у вас имеются данных 3 разных компаний в пределах одного сервера БД. Ваше приложение OpenXava, единожды развернутое на сервере приложений, может использоваться для сотрудников всех трех компаний, но при этом каждый сотрудник будет иметь доступ только к данным своей компании.
Использование нескольких схем БД полезно не только с точки зрения конфиденциальности данных, но также позволяет избежать разрастания таблиц БД. Вы можете отделять данные не только для разных компаний, но и разделять данные по другим принципам, например, создавать разные схемы БД для отдельных подразделений или хранить в разных схемах данные разных временных периодов, например, по годам.

Пример

Давайте посмотрим на пример модуля OpenXava, использующего multi-schema.
Как всегда, начнем с определения entity-класса:
 @Entity
 public class Issue {
 
   @Id @Column(length=5) @Required
   private String id;
 
   @Column(length=40) @Required
   private String description;
 
   public String getId() {
     return id;
   }
   public void setId(String id) {
     this.id = id;
   }
 
   public String getDescription() {
     return description;
   }
   public void setDescription(String description) {
     this.description = description;
   }
 
 }
Или, если вы используете XML компоненты OpenXava, то описание класса будет таким:
 <component name="Issue">
   <entity>
     <property name="id" type="String" key="true"
       size="5" required="true"/>
     <property name="description" type="String"
       size="40" required="true"/>
   </entity>
   <entity-mapping table="ISSUE">
     <property-mapping
       property="id" column="ID"/>
     <property-mapping
       property="description" column="DESCRIPTION"/>
   </entity-mapping>
 </component>
Обратите внимание, что наш класс связан с таблицей ISSUE, при этом мы не указали схемы БД (schema) для данной таблицы.
Теперь, нам необходимо определить модуль Issues в application.xml. Сделаем это:
 <module name="Issues">
   <model name="Issue"/>
   <controller name="Typical"/>
   <controller name="Issues"/>
 </module>
Также нам нужно определить контроллер Issues в файле controllers.xml:
 <controller name="Issues">
   <extends controller="DefaultSchema"/>
   <action name="changeToCompanyA" on-init="true"
     class="org.openxava.actions.SetDefaultSchemaAction">
     <set property="newDefaultSchema" value="COMPANYA"/>
     <use-object name="xava_defaultSchema"/>  <!-- Not needed since v4m2 -->
   </action>
   <action name="changeToCompanyB"
     class="org.openxava.actions.SetDefaultSchemaAction">
     <set property="newDefaultSchema" value="COMPANYB"/>
     <use-object name="xava_defaultSchema"/>  <!-- Not needed since v4m2 -->
   </action>
 </controller>
В результате при работе с нашим модулем, пользователь теперь может выбирать между схемами 'COMPANYA' и 'COMPANYB'. Делается это простым щелчком по соответствующей кнопке в пользовательском интерфейсе OX.

Вот и все решение.

Как это работает

OX имеет уже готовые к использованию контроллеры и действия (controllers and actions) для реализации данной функциональной возможности. Но если это необходимо вы можете реализовать свой вариант переключения между схемами, учитывающий ваши специфичные требования. Ключом реализации является класс XPersistence.Данный класс позволяет менять схему БД во время выполнения:
 XPersistence.setDefaultSchema("COMPANYA");
В вышеуказанном примере первоначальная схема БД сменяется на 'COMPANYA', но обратите внимание, что работает это только для текущего запроса.
Теперь, если создать сессионный объект (см. секцию 7.2 из Reference Guide) и настроить действие (action) с параметром on-each-request="true", то изменение схемы будет запоминаться для текущего потока данного пользователя.

Давайте попробуем решить эту проблему.
Определите сессионный объект для хранения текущей схемы БД, выбранной пользователем:
 <object name="xava_defaultSchema" class="java.lang.String" scope="global"/>
Вышеприведенный кусочек конфигурации уже определен в файле OpenXava/xava/default-controllers.xml, поэтому вы сразу можете использовать данный функционал. Тем не менее, если вам нужен свой вариант реализации, вы моежете создать свой собственный сессионный объект в файле controllers.xml вашего приложения. Ниже мы расскажем, как это сделать на примере уже реализованного функционала OX.

Определение действия (action) (для вашего собственного контроллера), которое будет выполняться перед каждым запросом, будет выглядеть так ( файл controllers.xml):
 <controller ... >
   <action name="setDefaultSchema" before-each-request="true" hidden="true"
     class="org.openxava.actions.SetDefaultSchemaAction">
     <use-object name="xava_defaultSchema"/>  <!-- Not needed since v4m2 -->
   </action>
 ...
 </controller>
(Для контроллера DefaultSchema уже сконфигурированно данное действие (action))
Для данного действия (action) вам нужно лишь определить сессионый объект(в данном случае xava_defaultSchema) и задать его как первичную схему БД , используя XPersistence. Ниже приведен код, реализованный в OX:
 public class SetDefaultSchemaAction extends BaseAction {
 
   @Inject  // Since v4m2
   private String defaultSchema;
   private String newDefaultSchema;
 
   public void execute() throws Exception {
     if (newDefaultSchema != null) defaultSchema = newDefaultSchema;
     XPersistence.setDefaultSchema(defaultSchema);
   }
 
   /**
    * The current default schema used by OpenXava and JPA.
    */
   public String getDefaultSchema() {
     return defaultSchema;
   }
 
   /**
    * The current default schema used by OpenXava and JPA.
    */
   public void setDefaultSchema(String company) {
     this.defaultSchema = company;
   }
 
   /**
    * The new default schema for OpenXava and JPA. <P>
    *
    * This value update the property 'defaultSchema'.
    */
   public String getNewDefaultSchema() {
     return newDefaultSchema;
   }
 
   /**
    * The new default schema for OpenXava and JPA. <P>
    *
    * This value update the property 'defaultSchema'.
    */
   public void setNewDefaultSchema(String newCompany) {
     this.newDefaultSchema = newCompany;
   }
 }
Из-за того, что defaultSchema введен, используя <use-object /> (in all OX versions) or @Inject (since v4m2), когда мы меняем свойство defaultSchema, мы также меняем сессионный объект (session object) xava_defaultSchema.
Данное действие (action) является часть. OpenXava (см. org.openxava.actions), вы можете использовать штатную реализацию или создать свою собственную, используя ту же техники.

Данная техника может использована и для XHibernate .

Теперь вы можете вызывать данное действие (или другое аналогичное), когда вы захотите сменить текущую схему БД для пользователя.